﻿#include "pch.h"
#include "TestLibDlg.h"
#include "TestAsync.h"

int CTestLibDlg::m_nValue;

CTestLibDlg::CTestLibDlg() : m_uiTimer([this] { OnUiTimer(); }), m_tpTimer([this] { OnTpTimer(); })
{
}

LRESULT CTestLibDlg::OnInitDialog(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
	CenterWindow(GetParent());
	return TRUE;
}

LRESULT CTestLibDlg::OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
	DestroyWindow();
	return 0;
}

void CTestLibDlg::OnFinalMessage(HWND)
{
	PostQuitMessage(0);
}

LRESULT CTestLibDlg::OnBtnClick1(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
{
	TestAsync::TestSyncAsync();
	TestAsync1();

	PostTask([]
	{
		ATLTRACE("PostTask: threadId=%d\n", GetCurrentThreadId());
		Sleep(1000);
	});

	PostTask(Sleep, 1000);
	PostTask(&CTestLibDlg::TestTask1, this, 1000);
	return 0;
}

LRESULT CTestLibDlg::OnBtnClick2(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
{
	TestAsync::TestException();
	return 0;
}

LRESULT CTestLibDlg::OnBtnClick3(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
{
	TestAsync2();
	PostDelayedTask(3000, TestAsync3, GetCurrentThreadId());
	return 0;
}

LRESULT CTestLibDlg::OnBtnClick4(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
{
	TestCaptureThread();
	return 0;
}

LRESULT CTestLibDlg::OnBtnClick5(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
{
	TestWrapFnAsAsync();
	m_uiTimer.Start(1000);
	m_tpTimer.Start(1024);
	PostDelayedTask(10000, [this] { m_tpTimer.Stop(); });
	return 0;
}

LRESULT CTestLibDlg::OnBtnClick6(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
{
	TestAsyncFile();
	TestAsyncFile2();
	return 0;
}

void CTestLibDlg::OnUiTimer() const
{
	assert(IsInMainThread());
	ATLTRACE("OnUiTimer: %d\n", GetTickCount());
}

void CTestLibDlg::OnTpTimer() const
{
	assert(IsInThreadPool());
	ATLTRACE("OnTpTimer: %d\n", GetTickCount());
}

void CTestLibDlg::TestTask1(int nDelay) const
{
	ATLTRACE("TestTask1: threadId=%d\n", GetCurrentThreadId());
	Sleep(nDelay);
}

Task<> CTestLibDlg::TestAsync1()
{
	DWORD dwThreadId1 = GetCurrentThreadId();
	co_await SwitchToThreadPool();
	DWORD dwThreadId2 = GetCurrentThreadId();
	assert(dwThreadId2 != dwThreadId1);
	co_await SwitchToThreadPool();
	assert(dwThreadId2 == GetCurrentThreadId());

	int v1 = co_await PostTask([]
	{
		Sleep(1000);
		return 111;
	});
	ATLTRACE("TestAsync1: v1=%d\n", v1);

	int v2 = co_await MyAsync1(true);
	ATLTRACE("TestAsync1: v2=%d\n", v2);

	int& v3 = co_await MyAsync3();
	assert(v3 == 100);
	m_nValue = 200;
	assert(v3 == 200);

	auto task = PostTask([]
	{
		Sleep(1000);
		return std::make_unique<int>(666);
	});

	if (task.Wait())
	{
		auto ptr = task.Result();
		ATLTRACE("TestAsync1: result=%d\n", *ptr);

		/*try
		{
			auto ptr2 = task.Result();
		}
		catch (const std::logic_error& e)
		{
			ATLTRACE("TestAsync1: %s\n", e.what());
		}*/
	}
}

Task<int> CTestLibDlg::MyAsync1(bool bAll)
{
	auto str = co_await MyAsync2();
	int val = std::stoi(str);

	if (!bAll)
		co_return val;

	auto ptr = co_await PostTask([]
	{
		Sleep(1000);
		return std::make_unique<int>(111);
	});
	val += *ptr;

	co_await DelayAsync(1000);
	co_return val;
}

Task<std::string> CTestLibDlg::MyAsync2()
{
	return PostTask([]
	{
		Sleep(1000);
		return std::string("222");
	});
}

Task<int&> CTestLibDlg::MyAsync3()
{
	co_await DelayAsync(1000);
	m_nValue = 100;
	co_return m_nValue;
}

Task<> CTestLibDlg::TestAsync2()
{
	DWORD dwMainThreadId = GetCurrentThreadId();
	ATLTRACE("TestAsync2 1: Main threadId=%d\n", dwMainThreadId);

	PostTask(ATID_DB, []
	{
		Sleep(1000);
		ATLTRACE("TestAsync2 2: DB threadId=%d\n", GetCurrentThreadId());
	});

	int val = co_await PostTask(ATID_DB, []
	{
		Sleep(1000);
		ATLTRACE("TestAsync2 3: DB threadId=%d\n", GetCurrentThreadId());
		return 123;
	});

	ATLTRACE("TestAsync2 4: DB threadId=%d, val=%d\n", GetCurrentThreadId(), val);
	assert(dwMainThreadId != GetCurrentThreadId());

	PostTask(ATID_MAIN, [dwMainThreadId]
	{
		ATLTRACE("TestAsync2 5: Main threadId=%d\n", dwMainThreadId);
		assert(dwMainThreadId == GetCurrentThreadId());
	});
}

Task<> CTestLibDlg::TestAsync3(DWORD dwMainThreadId)
{
	assert(dwMainThreadId != GetCurrentThreadId());
	co_await SwitchToMainThread();
	ATLTRACE("TestAsync3: Main threadId=%d\n", dwMainThreadId);
	assert(dwMainThreadId == GetCurrentThreadId());
	co_await SwitchToMainThread();
	assert(dwMainThreadId == GetCurrentThreadId());

	co_await DelayAsync(1000).CaptureThread();
	assert(IsInMainThread());

	co_await SwitchToTaskThread(ATID_DB);
	DWORD dwDBThreadId = GetCurrentThreadId();
	ATLTRACE("TestAsync3: DB threadId=%d\n", dwDBThreadId);
	assert(dwDBThreadId != dwMainThreadId);
	co_await SwitchToTaskThread(ATID_DB);
	assert(dwDBThreadId == GetCurrentThreadId());
}

Task<> CTestLibDlg::TestCaptureThread()
{
	ATLTRACE("TestCaptureThread 1: Main threadId=%d\n", GetCurrentThreadId());
	assert(IsInMainThread());

	int val = co_await PostTask([]
	{
		Sleep(1000);
		return 11;
	}).CaptureThread();

	ATLTRACE("TestCaptureThread 2: Main threadId=%d, value=%d\n", GetCurrentThreadId(), val);
	assert(IsInMainThread());

	co_await SwitchToThreadPool();
	ATLTRACE("TestCaptureThread 3: threadpoolId=%d\n", GetCurrentThreadId());
	assert(IsInThreadPool());

	val = co_await PostTask(ATID_DB, []
	{
		Sleep(1000);
		return 22;
	}).CaptureThread();

	ATLTRACE("TestCaptureThread 4: threadpoolId=%d, value=%d\n", GetCurrentThreadId(), val);
	assert(IsInThreadPool());

	val = co_await PostTask([]
	{
		Sleep(1000);
		return 33;
	}).CaptureThread();

	ATLTRACE("TestCaptureThread 5: threadpoolId=%d, value=%d\n", GetCurrentThreadId(), val);
	assert(IsInThreadPool());
}

void FakeHttpGet(LPCWSTR lpUrl, LPCWSTR lpHeaders, std::function<void(LPCSTR, int)>&& fnCallback)
{
	PostDelayedTask(1000, [fnCallback = std::move(fnCallback)]
	{
		fnCallback("Sample", 6);
	});
}

Task<std::string> FakeHttpGetAsync(LPCWSTR lpUrl, LPCWSTR lpHeaders)
{
	return WrapFnAsAsync<std::string>(FakeHttpGet, lpUrl, lpHeaders);
}

Task<> CTestLibDlg::TestWrapFnAsAsync()
{
	FakeHttpGet(L"http://", nullptr, [](LPCSTR lpData, int nSize)
	{
		ATLTRACE("TestWrapFnAsAsync: %d\n", nSize);
	});

	std::string strBody = co_await FakeHttpGetAsync(L"http://", nullptr);
	ATLTRACE("TestWrapFnAsAsync: %s\n", strBody.c_str());

	ATLTRACE("GetTickCount: %d\n", GetTickCount());
	co_await TaskWhenAll(DelayAsync(1000), DelayAsync(2000));
	ATLTRACE("GetTickCount: %d\n", GetTickCount());
	size_t nIndex = co_await TaskWhenAny(DelayAsync(1000), DelayAsync(2000));
	ATLTRACE("GetTickCount: %d\n", GetTickCount());
	assert(nIndex == 0);

	HANDLE hSemaphore = CreateSemaphore(nullptr, 0, 2, nullptr);
	PostDelayedTask(1000, ReleaseSemaphore, hSemaphore, 2, nullptr);
	DWORD dwResult = co_await WaitForAsync(hSemaphore, INFINITE);
	ATLTRACE("Wait result: %d\n", dwResult);
	dwResult = co_await WaitForAsync(hSemaphore, INFINITE);
	ATLTRACE("Wait result: %d\n", dwResult);
	CloseHandle(hSemaphore);
}

Task<> CTestLibDlg::TestAsyncFile()
{
	wchar_t szFileName[MAX_PATH];
	GetModuleFileName(NULL, szFileName, MAX_PATH);
	PathRemoveExtension(szFileName);
	PathAddExtension(szFileName, L".pdb");

	CAsyncFile fileIn;
	DWORD dwError = co_await fileIn.CreateAsync(szFileName);
	assert(dwError == 0);

	if (dwError)
	{
		ATLTRACE("Create file error: %d\n", dwError);
		co_return;
	}

	UINT nFileSize = (UINT)fileIn.GetFileSize();
	if (nFileSize == INVALID_FILE_SIZE)
		co_return;

	wcscat_s(szFileName, L".bak");
	CAsyncFile fileOut;
	dwError = co_await fileOut.CreateAsync(szFileName, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS);
	assert(dwError == 0);

	if (dwError)
	{
		ATLTRACE("Create file error: %d\n", dwError);
		co_return;
	}

	fileOut.SetFilePointer(nFileSize, FILE_BEGIN);
	fileOut.SetEndOfFile();
	fileOut.SetFilePointer(0, FILE_BEGIN);

	char szBuffer[2][4096];
	UINT nBytesRead = 0;
	auto task = fileIn.ReadAsync(szBuffer[0], sizeof(szBuffer[0]), &nBytesRead);

	for (int nIndex = 0;;)
	{
		dwError = co_await task;
		assert(dwError == 0 || dwError == ERROR_HANDLE_EOF);

		if (dwError)
		{
			ATLTRACE("Read file error: %d\n", dwError);
			break;
		}

		char *lpBuffer = szBuffer[nIndex];
		UINT nBytesToWrite = nBytesRead, nBytesWritten = 0;

		// Continue reading asynchronously while writing
		task = fileIn.ReadAsync(szBuffer[nIndex ^= 1], sizeof(szBuffer[0]), &nBytesRead);
		dwError = co_await fileOut.WriteAsync(lpBuffer, nBytesToWrite, &nBytesWritten);
		assert(dwError == 0 && nBytesToWrite == nBytesWritten);

		if (dwError)
		{
			ATLTRACE("Write file error: %d\n", dwError);
			break;
		}
	}

	assert(fileIn.GetFileSize() == fileOut.GetFileSize());
}

Task<> CTestLibDlg::TestAsyncFile2()
{
	wchar_t szFileName[MAX_PATH];
	GetModuleFileName(NULL, szFileName, MAX_PATH);

	std::string strData;
	DWORD dwError = co_await CAsyncFile::ReadAllAsync(szFileName, strData);
	assert(dwError == 0);

	if (dwError)
	{
		ATLTRACE("ReadAll file error: %d\n", dwError);
		co_return;
	}

	wcscat_s(szFileName, L".bak");
	dwError = co_await CAsyncFile::WriteAllAsync(szFileName, strData.c_str(), (UINT)strData.size());
	assert(dwError == 0);

	if (dwError)
		ATLTRACE("WriteAll file error: %d\n", dwError);
}
